use std::collections::HashMap;

use ndarray::array;
use rstest::rstest;

use super::super::*;
use crate::tests::fixtures::{
    rand_usize,
    random_connectors,
    random_missing_tile_policy,
    random_predecessor,
    random_tile_selection_policy,
    random_tile_text,
    random_tile_type,
    random_vacancy_policy,
    RANDOM_USIZE,
};

#[rstest]
#[case::no_slot((2, 3), None, HashMap::new())]
// region some slot, no tile, no neighbors
#[case::some_slot_no_tile_no_neighbors(
    (1, 1),
    Some(GridSlot {
        tile_id: None,
        direction: None,
        predecessor: Some((*RANDOM_USIZE + 2, *RANDOM_USIZE +7)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::from([
            Connectors { north: 4, east: 7, south: 9, west: 8 },
            Connectors { north: 8, east: 4, south: 9, west: 2 },
        ]),
    }),
    HashMap::new(),
)]
// endregion
// region some slot, some tile, no neighbors
#[case::some_slot_some_tile_no_neighbors(
    (1, 3),
    Some(GridSlot {
        tile_id: None,
        direction: None,
        predecessor: Some((*RANDOM_USIZE + 9, *RANDOM_USIZE * 19)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::from([
            Connectors { north: 9, east: 2, south: 1, west: 4 },
            Connectors { north: 3, east: *RANDOM_USIZE + 9, south: 4, west: 4 },
            Connectors {
                north: *RANDOM_USIZE + 8,
                east: *RANDOM_USIZE + 5,
                south: *RANDOM_USIZE + 9,
                west: *RANDOM_USIZE + 6,
            }
        ]),
    }),
    HashMap::new(),
)]
// endregion
// region some slot, some tile, one neighbor
#[case::some_slot_some_tile_one_neighbor(
    (6, 3),
    Some(GridSlot {
        tile_id: None,
        direction: None,
        predecessor: Some((5, 7)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::from([
            Connectors {
                north: 1,
                east: 2,
                south: 1,
                west: 1,
            },
            Connectors {
                north: *RANDOM_USIZE + 2,
                east: *RANDOM_USIZE + 9,
                south: *RANDOM_USIZE + 9,
                west: *RANDOM_USIZE + 5,
            },
        ]),
    }),
    {
        let mut map = HashMap::new();
        map.insert(
            (6, 2),
            GridSlot {
                tile_id: Some(1),
                direction: Some(Direction::East),
                predecessor: Some((8, 2)),
                exposed_edges: vec![Direction::North, Direction::West, Direction::East],
                excluded_connectors: HashSet::from([
                    Connectors {
                        north: 1,
                        east: 2,
                        south: 1,
                        west: 1,
                    },
                    Connectors {
                        north: 2,
                        east: 2,
                        south: 1,
                        west: 1,
                    },
                ]),
            },
        );
        map
    },
)]
// endregion
// region some slot, some tile, two neighbors, two non-blank connectors
#[case::some_slot_some_tile_two_neighbors_two_non_blank_connectors(
    (6, 2),
    Some(GridSlot {
        tile_id: None,
        direction: None,
        predecessor: Some((8, 2)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::from([
            Connectors {
                north: 1,
                east: 2,
                south: 1,
                west: 1,
            },
            Connectors {
                north: 2,
                east: 2,
                south: 1,
                west: 1,
            },
            Connectors {
                north: 6,
                east: 5,
                south: 5,
                west: 8,
            },
        ]),
    }),
    {
        let mut map = HashMap::new();
        map.insert(
            (6, 3),
            GridSlot {
                tile_id: Some(2),
                direction: Some(Direction::North),
                predecessor: Some((5, 7)),
                exposed_edges: vec![Direction::North, Direction::West],
                excluded_connectors: HashSet::from([Connectors {
                    north: 1,
                    east: 2,
                    south: 1,
                    west: 1,
                }]),
            },
        );
        map.insert(
            (6, 1),
            GridSlot {
                tile_id: Some(6),
                direction: Some(Direction::South),
                predecessor: Some((7, 8)),
                exposed_edges: vec![Direction::East],
                excluded_connectors: HashSet::from([
                    Connectors {
                        north: 7,
                        east: 9,
                        south: 6,
                        west: 5,
                    },
                ]),
            },
        );
        map
    },
)]
// endregion
// region some slot, some tile, four neighbors, four non-blank connectors
#[case::some_slot_some_tile_four_neighbors_four_non_blank_connectors(
    (3, 2),
    Some(GridSlot {
        tile_id: None,
        direction: None,
        predecessor: Some((17, *RANDOM_USIZE + 14)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::from([
            Connectors {
                north: 2,
                east: 6,
                south: 5,
                west: 2,
            },
            Connectors {
                north: 3,
                east: 4,
                south: 2,
                west: 3,
            },
        ]),
    }),
    {
        let mut map = HashMap::new();
        map.insert(
            (2, 2),
            GridSlot {
                tile_id: Some(80),
                direction: Some(Direction::North),
                predecessor: Some((1, 7)),
                exposed_edges: vec![Direction::East, Direction::South],
                excluded_connectors: HashSet::from([
                    Connectors {
                        north: 4,
                        east: 6,
                        south: 9,
                        west: 5,
                    },
                ]),
            },
        );
        map.insert(
            (3, 1),
            GridSlot {
                tile_id: Some(18),
                direction: Some(Direction::West),
                predecessor: Some((4, 8)),
                exposed_edges: vec![Direction::North, Direction::East],
                excluded_connectors: HashSet::new(),
            },
        );
        map.insert(
            (3, 3),
            GridSlot {
                tile_id: Some(83),
                direction: Some(Direction::North),
                predecessor: None,
                exposed_edges: vec![Direction::West],
                excluded_connectors: HashSet::from([
                    Connectors {
                        north: 4,
                        east: 6,
                        south: 7,
                        west: 8,
                    },
                ]),
            },
        );
        map.insert(
            (4, 2),
            GridSlot {
                tile_id: Some(3),
                direction: Some(Direction::North),
                predecessor: Some((454, 28)),
                exposed_edges: vec![Direction::South, Direction::North],
                excluded_connectors: HashSet::new(),
            },
        );
        map
    },
)]
// endregion
// region some slot, some tile, three neighbors, one non-blank connector
#[case::some_slot_some_tile_three_neighbors_one_non_blank_connector(
    (6, 1),
    Some(GridSlot {
        tile_id: None,
        direction: None,
        predecessor: Some((7, 8)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::from([
            Connectors {
                north: 7,
                east: 9,
                south: 6,
                west: 5,
            },
            Connectors {
                north: *RANDOM_USIZE + 5,
                east: 1,
                south: 1,
                west: *RANDOM_USIZE + 2,
            },
        ]),
    }),
    {
        let mut map = HashMap::new();
        map.insert(
            (5, 1),
            GridSlot {
                tile_id: Some(*RANDOM_USIZE + 14),
                direction: Some(Direction::West),
                predecessor: Some((8434, 275)),
                exposed_edges: vec![Direction::South],
                excluded_connectors: HashSet::from([Connectors {
                    north: 6,
                    east: 9,
                    south: 9,
                    west: 7,
                }]),
            },
        );
        map
    },
)]
// endregion
// region some slot, some tile, two neighbors, no non-blank connector
#[case::some_slot_some_tile_two_neighbors_no_non_blank_connector(
    (4, 3),
    Some(GridSlot {
        tile_id: None,
        direction: None,
        predecessor: Some((399, 188)),
        exposed_edges: vec![],
        excluded_connectors: HashSet::from([
            Connectors {
                north: 1,
                east: *RANDOM_USIZE + 7,
                south: *RANDOM_USIZE + 8,
                west: 1,
            },
        ]),
    }),
    HashMap::new(),
)]
// endregion
fn test_reset_current_slot(
    #[case] current_slot: (usize, usize),
    #[case] expected_slot: Option<GridSlot>,
    #[case] affected_neighbors: HashMap<(usize, usize), GridSlot>,
) {
    let tile_list = TileList {
        tiles: vec![
            random_tile_text(random_connectors()),
            random_tile_text(Connectors {
                north: 6,
                east: 5,
                south: 5,
                west: 8,
            }),
            random_tile_text(Connectors {
                north: *RANDOM_USIZE + 2,
                east: *RANDOM_USIZE + 9,
                south: *RANDOM_USIZE + 9,
                west: *RANDOM_USIZE + 5,
            }),
            random_tile_text(random_connectors()),
            random_tile_text(Connectors {
                north: *RANDOM_USIZE + 8,
                east: *RANDOM_USIZE + 5,
                south: *RANDOM_USIZE + 9,
                west: *RANDOM_USIZE + 6,
            }),
            random_tile_text(Connectors {
                north: 3,
                east: 4,
                south: 2,
                west: 3,
            }),
            random_tile_text(Connectors {
                north: *RANDOM_USIZE + 5,
                east: 1,
                south: 1,
                west: *RANDOM_USIZE + 2,
            }),
            random_tile_text(Connectors {
                north: 1,
                east: *RANDOM_USIZE + 7,
                south: *RANDOM_USIZE + 8,
                west: 1,
            }),
        ],
        horizontal_connector_names: vec![],
        vertical_connector_names: vec![],
        tile_type: random_tile_type(),
    };
    let mut slot_grid = SlotGrid {
        grid: array![
            [None, None, None, None],
            [
                None,
                Some(GridSlot {
                    tile_id: None,
                    direction: Some(Direction::North),
                    predecessor: Some((*RANDOM_USIZE + 2, *RANDOM_USIZE + 7,)),
                    exposed_edges: vec![Direction::South, Direction::East],
                    excluded_connectors: HashSet::from([
                        Connectors {
                            north: 4,
                            east: 7,
                            south: 9,
                            west: 8
                        },
                        Connectors {
                            north: 8,
                            east: 4,
                            south: 9,
                            west: 2
                        },
                    ]),
                }),
                None,
                Some(GridSlot {
                    tile_id: Some(4),
                    direction: Some(Direction::West),
                    predecessor: Some((*RANDOM_USIZE + 9, *RANDOM_USIZE * 19,)),
                    exposed_edges: vec![Direction::South, Direction::West],
                    excluded_connectors: HashSet::from([
                        Connectors {
                            north: 9,
                            east: 2,
                            south: 1,
                            west: 4
                        },
                        Connectors {
                            north: 3,
                            east: *RANDOM_USIZE + 9,
                            south: 4,
                            west: 4
                        },
                    ]),
                }),
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(80),
                    direction: Some(Direction::North),
                    predecessor: Some((1, 7)),
                    exposed_edges: vec![Direction::East],
                    excluded_connectors: HashSet::from([Connectors {
                        north: 4,
                        east: 6,
                        south: 9,
                        west: 5,
                    }]),
                }),
                None,
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(18),
                    direction: Some(Direction::West),
                    predecessor: Some((4, 8)),
                    exposed_edges: vec![Direction::North],
                    excluded_connectors: HashSet::new(),
                }),
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: Some(Direction::South),
                    predecessor: Some((17, *RANDOM_USIZE + 14)),
                    exposed_edges: vec![],
                    excluded_connectors: HashSet::from([Connectors {
                        north: 2,
                        east: 6,
                        south: 5,
                        west: 2,
                    }]),
                }),
                Some(GridSlot {
                    tile_id: Some(83),
                    direction: Some(Direction::North),
                    predecessor: None,
                    exposed_edges: vec![],
                    excluded_connectors: HashSet::from([Connectors {
                        north: 4,
                        east: 6,
                        south: 7,
                        west: 8,
                    }]),
                }),
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(3),
                    direction: Some(Direction::North),
                    predecessor: Some((454, 28)),
                    exposed_edges: vec![Direction::South],
                    excluded_connectors: HashSet::new(),
                }),
                Some(GridSlot {
                    tile_id: Some(7),
                    direction: Some(Direction::East),
                    predecessor: Some((399, 188)),
                    exposed_edges: vec![],
                    excluded_connectors: HashSet::new(),
                }),
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(*RANDOM_USIZE + 14),
                    direction: Some(Direction::West),
                    predecessor: Some((8434, 275)),
                    exposed_edges: vec![],
                    excluded_connectors: HashSet::from([Connectors {
                        north: 6,
                        east: 9,
                        south: 9,
                        west: 7,
                    }]),
                }),
                None,
                None
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(6),
                    direction: Some(Direction::South),
                    predecessor: Some((7, 8)),
                    exposed_edges: vec![],
                    excluded_connectors: HashSet::from([Connectors {
                        north: 7,
                        east: 9,
                        south: 6,
                        west: 5,
                    }]),
                }),
                Some(GridSlot {
                    tile_id: Some(1),
                    direction: Some(Direction::East),
                    predecessor: Some((8, 2)),
                    exposed_edges: vec![Direction::North, Direction::West],
                    excluded_connectors: HashSet::from([
                        Connectors {
                            north: 1,
                            east: 2,
                            south: 1,
                            west: 1,
                        },
                        Connectors {
                            north: 2,
                            east: 2,
                            south: 1,
                            west: 1,
                        }
                    ]),
                }),
                Some(GridSlot {
                    tile_id: Some(2),
                    direction: Some(Direction::North),
                    predecessor: Some((5, 7)),
                    exposed_edges: vec![Direction::North],
                    excluded_connectors: HashSet::from([Connectors {
                        north: 1,
                        east: 2,
                        south: 1,
                        west: 1,
                    }]),
                }),
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(*RANDOM_USIZE + 534),
                    direction: Some(Direction::West),
                    predecessor: Some((500, 342)),
                    exposed_edges: vec![Direction::East],
                    excluded_connectors: HashSet::from([Connectors {
                        north: *RANDOM_USIZE + 4,
                        east: *RANDOM_USIZE + 6,
                        south: *RANDOM_USIZE + 9,
                        west: *RANDOM_USIZE + 9,
                    }]),
                }),
                None,
                None
            ],
        ],
        num_rows: 7,
        num_cols: 3,
        border_tile_id: Some(*RANDOM_USIZE * 12),
        tile_connections: TileConnectionArray::new(&tile_list.tiles),
        tile_list,
        missing_tile_policy: random_missing_tile_policy(),
        tile_selection_policy: random_tile_selection_policy(),
        vacancy_policy: random_vacancy_policy(),
        current_slot,
        current_predecessor: random_predecessor(),
        last_leaves: vec![],
        exposed_edge_count: rand_usize(0, 9),
    };

    let grid_clone = slot_grid.grid.clone();

    // Run multiple times to ensure idempotency.
    for _ in 1..=rand_usize(1, 3) {
        let _: () = slot_grid.reset_current_slot();
    }

    for (((row, col), slot), expected_unchanged_slot) in
        slot_grid.grid.indexed_iter().zip(grid_clone.iter())
    {
        if (row, col) == current_slot {
            assert_eq!(*slot, expected_slot);
        } else if affected_neighbors.contains_key(&(row, col)) {
            assert_eq!(
                *slot
                    .as_ref()
                    .expect("the slot should exist in slot_grid.grid"),
                affected_neighbors[&(row, col)]
            );
        } else {
            assert_eq!(slot, expected_unchanged_slot);
        }
    }
}
